// 版权2019 etcd作者
// 
// 根据Apache许可证2.0版（以下简称“许可证”）获得许可；
// 除非遵守许可证，否则不得使用此文件。
// 您可以通过
// 
// www.apache.org/licenses/LICENSE-2.0 
// 
// 除非适用法律要求或书面同意，否则根据许可证分发的软件
// 按“原样”分发，
// 无任何明示或暗示的保证或条件。
// 请参阅许可证，了解管理许可的特定语言和
// 许可证下的限制。

package raftpb

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/gogo/protobuf/proto"
)

// ConfChangeI对ConfChangeV2和（遗留）ConfChange进行抽象，以允许
// 以统一的方式处理它们。
type ConfChangeI interface {
	AsV2() ConfChangeV2
	AsV1() (ConfChange, bool)
}

// MarshalConfChange调用基础ConfChange或confchange2 
// 上的Marshal，并返回结果和相应的EntryType。
func MarshalConfChange(c ConfChangeI) (EntryType, []byte, error) {
	var typ EntryType
	var ccdata []byte
	var err error
	if ccv1, ok := c.AsV1(); ok {
		typ = EntryConfChange
		ccdata, err = ccv1.Marshal()
	} else {
		ccv2 := c.AsV2()
		typ = EntryConfChangeV2
		ccdata, err = ccv2.Marshal()
	}
	return typ, ccdata, err
}

// AsV2返回执行相同操作的V2配置更改。
func (c ConfChange) AsV2() ConfChangeV2 {
	return ConfChangeV2{
		Changes: []ConfChangeSingle{{
			Type:   c.Type,
			NodeID: c.NodeID,
		}},
		Context: c.Context,
	}
}

// AsV1返回ConfChange和true。
func (c ConfChange) AsV1() (ConfChange, bool) {
	return c, true
}

// AsV2是标识。
func (c ConfChangeV2) AsV2() ConfChangeV2 { return c }

// AsV1返回ConfChange{}和false。
func (c ConfChangeV2) AsV1() (ConfChange, bool) { return ConfChange{}, false }

// EnterJoint返回两个布尔值。当且仅当此
// 配置更改将使用联合一致意见时，第二个布尔值为真，如果其包含多个
// 更改，或者如果明确要求使用联合一致意见，则为真。
// 第一个布尔值只有在第二个布尔值为真时才为真，并指示
// 联合状态是否将自动离开。
func (c ConfChangeV2) EnterJoint() (autoLeave bool, ok bool) {
	// NB：理论上，更多的配置更改可能符合“简单”
	// 协议的要求，但这取决于更改应用的配置。
	// 例如，如果两个节点都是
	// 基本配置的一部分，则添加两个学习者是不正确的（即，在
	// 应用配置更改的过程中，两个投票者变成了学习者）。在实践中，这些区别并不重要，因此我们保持简单，并自由地使用联合共识。
	if c.Transition != ConfChangeTransitionAuto || len(c.Changes) > 1 {
		// 使用共同协商一致意见。
		var autoLeave bool
		switch c.Transition {
		case ConfChangeTransitionAuto:
			autoLeave = true
		case ConfChangeTransitionJointImplicit:
			autoLeave = true
		case ConfChangeTransitionJointExplicit:
		default:
			panic(fmt.Sprintf("unknown transition: %+v", c))
		}
		return autoLeave, true
	}
	return false, false
}

// LeaveJoint如果配置更改留下关节配置，则为true。
// 如果ConfChangeV2为零，则会出现这种情况，但上下文字段中可能存在异常
// 情况。
func (c ConfChangeV2) LeaveJoint() bool {
	// NB:c已经是副本。
	c.Context = nil
	return proto.Equal(&c, &ConfChangeV2{})
}

// ConfChangesFromString将以空格分隔的操作序列解析为一个
// confchangessingle的切片。支持的操作有：
// /-vn:make n a voter，
// /-ln:make n a learner，
// /-rn:remove n和
// /-un:update n。
func ConfChangesFromString(s string) ([]ConfChangeSingle, error) {
	var ccs []ConfChangeSingle
	toks := strings.Split(strings.TrimSpace(s), " ")
	if toks[0] == "" {
		toks = nil
	}
	for _, tok := range toks {
		if len(tok) < 2 {
			return nil, fmt.Errorf("unknown token %s", tok)
		}
		var cc ConfChangeSingle
		switch tok[0] {
		case 'v':
			cc.Type = ConfChangeAddNode
		case 'l':
			cc.Type = ConfChangeAddLearnerNode
		case 'r':
			cc.Type = ConfChangeRemoveNode
		case 'u':
			cc.Type = ConfChangeUpdateNode
		default:
			return nil, fmt.Errorf("unknown input: %s", tok)
		}
		id, err := strconv.ParseUint(tok[1:], 10, 64)
		if err != nil {
			return nil, err
		}
		cc.NodeID = id
		ccs = append(ccs, cc)
	}
	return ccs, nil
}

// ConfChangesToString与ConfChangesFromString相反。
func ConfChangesToString(ccs []ConfChangeSingle) string {
	var buf strings.Builder
	for i, cc := range ccs {
		if i > 0 {
			buf.WriteByte(' ')
		}
		switch cc.Type {
		case ConfChangeAddNode:
			buf.WriteByte('v')
		case ConfChangeAddLearnerNode:
			buf.WriteByte('l')
		case ConfChangeRemoveNode:
			buf.WriteByte('r')
		case ConfChangeUpdateNode:
			buf.WriteByte('u')
		default:
			buf.WriteString("unknown")
		}
		fmt.Fprintf(&buf, "%d", cc.NodeID)
	}
	return buf.String()
}
